14. 입출력 함수¶
14.1 파일 읽기¶
학생들의 성적이 담긴 CSV 파일을 받았다고 하겠습니다. 이 파일을 R로 읽어 데이터프레임으로 만들어야 분석을 시작할 수 있는데, 파일마다 값을 구분하는 기호(쉼표·탭·공백 등)나 소수점 표기, 첫 줄이 변수 이름인지 여부가 제각각이라 하나의 함수만으로는 모든 경우를 처리하기 어렵습니다.
실습에 사용할 파일은 다음 코드로 미리 만들어 둡니다(파일을 만드는 write.csv()는 다음 절에서 자세히 다룹니다).
성적 <- data.frame(
이름 = c("김민준", "이서연", "박도윤", "최지우", "정하은"),
국어 = c(88, 95, 76, 90, 82),
영어 = c(92, 88, 90, 74, 85),
수학 = c(79, 84, 95, 88, 91)
)
write.csv(성적, "성적.csv", row.names = FALSE)
만들어진 파일의 내용은 다음과 같습니다(readLines()는 이 절 뒤에서 다시 다룹니다).
cat(readLines("성적.csv"), sep = "\n")
#> "이름","국어","영어","수학"
#> "김민준",88,92,79
#> "이서연",95,88,84
#> "박도윤",76,90,95
#> "최지우",90,74,88
#> "정하은",82,85,91
이 파일을 읽어 들여 과목별 평균을 계산하는 것이 이 절과 다음 절(14.2)에서 이어질 실습의 큰 흐름입니다.
성적 <- read.csv("성적.csv")
성적$평균 <- round(rowMeans(성적[2:4]), 1)
성적
#> 이름 국어 영어 수학 평균
#> 1 김민준 88 92 79 86.3
#> 2 이서연 95 88 84 89.0
#> 3 박도윤 76 90 95 87.0
#> 4 최지우 90 74 88 84.0
#> 5 정하은 82 85 91 86.0
이 절의 함수들은 크게 "값이 표(table) 형태로 정리된 파일을 읽는가"와 "구분자 없이 줄(line) 그대로 읽는가"로 나뉩니다. 전자는 read.table()과 그 wrapper 함수들(read.csv() 등), read.fwf(), scan()이 맡고, 후자는 readLines()가 맡습니다. 여기에 R 객체 자체를 그대로 복원하는 readRDS()가 더해집니다.
read.table()¶
read.table(file, header = FALSE, sep = "", quote = "\"'", dec = ".", na.strings = "NA", colClasses = NA, skip = 0, nrows = -1, check.names = TRUE, stringsAsFactors = FALSE, fileEncoding = "", encoding = "unknown", ...)는 텍스트 파일 file을 읽어 데이터프레임으로 반환하는, 이 절의 가장 기본이 되는 함수입니다.
file: 읽어 들일 파일의 경로(문자열) 또는 연결(connection) 객체."http://"·"https://"로 시작하는 인터넷 주소를 그대로 넣으면 웹에 있는 파일도 곧바로 읽을 수 있습니다(자세한 내용은 아래 "더 알아보기" 참고).header: 파일의 첫 줄을 변수(열) 이름으로 볼지 여부. 기본값은FALSE이므로, 첫 줄이 데이터로 잘못 섞여 들어가지 않도록 하려면 반드시TRUE로 지정해야 합니다.sep: 값을 구분하는 구분자. 기본값""은 공백·탭 등 하나 이상의 공백류 문자를 구분자로 봅니다. 쉼표로 구분된 파일이라면sep = ","를 지정해야 합니다.quote: 문자열 값을 감싸는 따옴표로 인식할 문자들. 기본값은 큰따옴표(")와 작은따옴표(') 둘 다입니다.dec: 소수점으로 쓸 기호. 기본값은 마침표(.)이며, 쉼표(,)를 소수점으로 쓰는 자료라면dec = ","로 바꿔야 합니다.na.strings: 결측치(NA)로 처리할 문자열들. 기본값은"NA"하나뿐이므로, 자료에서 결측을"-"나 빈 칸 등 다른 기호로 표시했다면 이 인자로 직접 지정해야 합니다.colClasses: 각 열의 자료형을 미리 지정합니다. 지정하지 않으면(기본값NA) R이 각 열의 값을 보고 자동으로 자료형을 추측합니다.skip: 파일 맨 앞에서 건너뛸 줄 수. 파일 위쪽에 안내문 등이 붙어 있을 때 유용합니다.nrows: 최대로 읽어 들일 행 수. 매우 큰 파일에서 앞부분 일부만 미리 살펴보고 싶을 때 지정합니다.check.names:TRUE(기본값)이면 열 이름이 R 변수명 규칙에 맞지 않을 때(공백·특수문자 포함 등)make.names()로 자동 변환합니다.stringsAsFactors: 문자열 열을 팩터(factor)로 변환할지 여부. R 4.0.0부터 기본값이FALSE로 바뀌어, 이제는 별도로 지정하지 않는 한 문자열이 문자형(character) 그대로 유지됩니다.fileEncoding,encoding: 파일이 R 세션과 다른 문자 인코딩(예:"CP949","UTF-8")으로 저장되어 있을 때 지정합니다.
앞서 만든 파일은 쉼표(,)로 구분되어 있으므로, read.table()로 읽으려면 sep과 header를 직접 지정해야 합니다.
d1 <- read.table("성적.csv", header = TRUE, sep = ",")
str(d1)
#> 'data.frame': 5 obs. of 4 variables:
#> $ 이름: chr "김민준" "이서연" "박도윤" "최지우" ...
#> $ 국어: int 88 95 76 90 82
#> $ 영어: int 92 88 90 74 85
#> $ 수학: int 79 84 95 88 91
이름 열이 chr(문자형)로 표시된 점에 주목해야 합니다. R 4.0 이전이었다면 stringsAsFactors의 기본값이 TRUE여서 이 열이 팩터(Factor)로 바뀌었을 것입니다.
read.csv() / read.csv2() / read.delim() / read.delim2()¶
read.table()은 모든 옵션을 직접 지정해야 하므로, 자주 쓰이는 파일 형식마다 sep·header·dec의 기본 조합을 미리 정해 둔 네 가지 wrapper 함수가 함께 제공됩니다. 넷 다 내부적으로는 read.table()을 호출할 뿐이며, read.table()의 다른 인자(na.strings, colClasses, fileEncoding 등)도 그대로 추가로 넘길 수 있습니다.
| 함수 | sep 기본값 |
header 기본값 |
dec 기본값 |
주로 쓰는 상황 |
|---|---|---|---|---|
read.csv() |
"," |
TRUE |
"." |
쉼표로 구분된 CSV(한국·영어권 표준) |
read.csv2() |
";" |
TRUE |
"," |
세미콜론으로 구분, 소수점에 쉼표를 쓰는 유럽식 CSV |
read.delim() |
"\t"(탭) |
TRUE |
"." |
탭으로 구분된 파일 |
read.delim2() |
"\t"(탭) |
TRUE |
"," |
탭 구분 + 유럽식 소수점 |
앞서 read.table()로 지정했던 옵션들이 read.csv()에는 이미 기본값으로 들어 있으므로, 다음 두 코드는 완전히 같은 결과를 냅니다.
read.csv2()·read.delim2()가 필요한 이유는 "쉼표"의 역할이 나라마다 다르기 때문입니다. 예를 들어 다음처럼 세미콜론으로 구분하고 소수점에 쉼표를 쓰는 파일(eu.csv)이 있다고 하겠습니다.
주의: 이런 파일을
read.csv()로(즉sep = ","로) 읽으면 오류 없이 조용히 잘못된 결과를 만들어 냅니다. 헤더 줄(제품;가격)에는 쉼표가 없어 열이 1개로 인식되는데, 데이터 줄(사과;1500,5)에는 쉼표가 있어 열이 2개로 나뉩니다. 이렇게 데이터의 열 개수가 헤더보다 하나 더 많으면 R은 첫 번째 열을 행 이름(row name)으로 간주해 버립니다.wrong <- read.csv("eu.csv") wrong rownames(wrong) #> 제품.가격 #> 사과;1500 5 #> 바나나;2300 0 #> 포도;4200 75 #> [1] "사과;1500" "바나나;2300" "포도;4200"원래 필요했던 "제품"·"가격" 두 열이 아니라,
제품.가격이라는 열 하나(소수점 이하 자릿수만 남음)와 엉뚱한 행 이름만 남은 것을 볼 수 있습니다.read.csv2()로 읽으면 의도한 대로 처리됩니다.
read.fwf()¶
read.fwf(file, widths, header = FALSE, skip = 0, col.names, colClasses = NA, strip.white = FALSE, ...)는 구분자 없이 자리(고정폭, fixed width)로만 값이 나뉘어 있는 파일을 읽습니다("fixed width file"의 약자).
file: 읽어 들일 파일 경로.widths: 각 열이 차지하는 문자 수(폭)를 나타내는 정수 벡터. 예를 들어c(4, 3, 4)는 "처음 4글자는 1열, 다음 3글자는 2열, 다음 4글자는 3열"이라는 뜻입니다. 음수를 넣으면 그만큼의 글자를 건너뛰고(무시하고) 넘어갑니다.header,skip:read.table()과 동일합니다.col.names: 각 열에 붙일 이름. 지정하지 않으면V1,V2, ... 형태의 기본 이름이 붙습니다.colClasses:read.table()과 동일하게 열의 자료형을 미리 지정합니다.strip.white:TRUE로 지정하면 각 값의 앞뒤 공백을 잘라내려 시도합니다. 다만 실제로는 큰따옴표로 감싸지 않은 값에만 부분적으로 적용되므로, 아래 예제처럼trimws()로 직접 다듬는 편이 더 확실합니다.
사원 정보가 구분자 없이 정해진 자리마다 값이 채워진 파일(사원번호 4자리, 이름 3자리, 부서 4자리)이 있다고 하겠습니다.
d <- read.fwf("사원.txt", widths = c(4, 3, 4),
col.names = c("사원번호", "이름", "부서"))
d
#> 사원번호 이름 부서
#> 1 1001 김민준 영업
#> 2 1002 이서연 기획
#> 3 1003 박도윤 총무
부서 열의 값 끝에 원본 파일의 여백이 그대로 남아 있는 것을 볼 수 있습니다. trimws()(12장)로 직접 다듬어 줍니다.
한글처럼 한 글자가 화면에서 두 칸을 차지하는 문자가 섞인 파일의 폭을 셀 때는, widths가 화면상의 "칸 수"가 아니라 문자(글자) 수 기준이라는 점에 유의해야 합니다.
scan()¶
scan(file = "", what = double(), sep = "", na.strings = "NA", skip = 0, nlines = 0, quiet = FALSE, text, ...)는 파일이나 콘솔에서 데이터를 읽어 벡터(또는 지정한 구조)로 반환하는, 이 절에서 가장 저수준(low-level)인 함수입니다. 실제로 read.table() 내부에서도 scan()이 쓰입니다.
file: 읽어 들일 파일 경로. 빈 문자열(기본값)이면 콘솔에서 직접 입력을 받습니다(대화형 세션에서만 의미가 있습니다).what: 읽어 들일 값의 자료형을 알려 주는 "본보기"입니다. 기본값double()은 숫자로 읽으라는 뜻이고,character()를 지정하면 문자형으로 읽습니다.list(...)를 넘기면 여러 열을 한 번에 각기 다른 자료형으로 읽을 수도 있습니다.sep: 값을 구분하는 구분자. 기본값""은 공백류 문자를 구분자로 봅니다.na.strings:read.table()과 동일합니다.skip,nlines: 건너뛸 줄 수와 읽어 들일 최대 줄 수.quiet:FALSE(기본값)이면 몇 개의 값을 읽었는지("Read N items")를 메시지로 알려 줍니다. 스크립트에서 이 메시지가 거슬린다면TRUE로 지정합니다.text: 파일 대신 문자열 자체에서 곧바로 읽고 싶을 때 사용합니다(file대신 지정).
콘솔에는 몇 개의 값을 읽었는지 알려 주는 메시지(Read 6 items)가 함께 표시됩니다. 파일이 아니라 문자열 자체를 바로 읽어야 할 때는 text 인자가 편리합니다.
read.table()이 이미 데이터프레임이라는 완성된 형태로 결과를 주는 반면, scan()은 더 단순하고 유연한 대신 구조를 직접 만들어야 하는 저수준 함수입니다. 정형화된 표 형태의 자료라면 read.table() 계열이, 벡터 하나만 빠르게 읽으면 되는 상황이라면 scan()이 더 간단합니다.
readLines()¶
readLines(con, n = -1L, encoding = "unknown", warn = TRUE)는 텍스트 파일을 값 단위가 아니라 줄(line) 단위 그대로, 문자형 벡터로 읽어 들입니다.
con: 읽어 들일 파일 경로 또는 연결(connection) 객체.n: 읽어 들일 최대 줄 수. 기본값-1L은 파일 끝까지 전부 읽으라는 뜻입니다.encoding: 파일의 문자 인코딩을 지정합니다.warn:TRUE(기본값)이면 파일 끝에 개행 문자가 없는 등 형식이 살짝 어긋난 경우 경고를 띄웁니다.
read.table()·scan()이 값을 특정 자료형(숫자·문자 등)으로 해석해서 읽는 반면, readLines()는 해석을 전혀 하지 않고 각 줄을 있는 그대로 문자열로 가져온다는 점이 다릅니다. 그래서 정형화되지 않은 텍스트(로그 파일, 설정 파일, 형식이 일정하지 않은 문서 등)를 다룰 때, 또는 본격적으로 파싱하기 전에 파일의 실제 내용을 먼저 눈으로 확인하고 싶을 때 유용합니다.
lines <- readLines("성적.csv")
lines
readLines("성적.csv", n = 2) # 처음 두 줄만
#> [1] "\"이름\",\"국어\",\"영어\",\"수학\"" "\"김민준\",88,92,79"
#> [3] "\"이서연\",95,88,84" "\"박도윤\",76,90,95"
#> [5] "\"최지우\",90,74,88" "\"정하은\",82,85,91"
#> [1] "\"이름\",\"국어\",\"영어\",\"수학\"" "\"김민준\",88,92,79"
더 알아보기: 한글 파일과 인코딩 문제
한국에서 만들어진 텍스트·CSV 파일은 UTF-8뿐 아니라 CP949(구 EUC-KR 계열, 흔히 "Windows-949"라고도 함)로 저장된 경우가 여전히 많습니다. 특히 Windows 엑셀에서 "CSV(쉼표로 분리)" 형식으로 저장하면 기본적으로 CP949로 저장되고, "CSV UTF-8(쉼표로 분리)" 형식으로 저장해야 UTF-8이 됩니다. 두 인코딩이 섞이면 한글이 깨지거나, 아래처럼 아예 오류가 나기도 합니다.
tryCatch( read.csv("cp949.csv"), # UTF-8 세션에서 CP949로 저장된 파일을 그냥 읽으면 error = function(e) cat("에러:", conditionMessage(e), "\n") ) #> 에러: invalid multibyte string at '<c0>><a7>'
fileEncoding인자에 파일의 실제 인코딩을 지정하면 해결됩니다.파일이 어떤 인코딩인지 전혀 모르겠다면,
readLines(..., encoding = "CP949")처럼 몇 가지 후보를 바꿔가며 원문이 제대로 보이는지 눈으로 확인해 보는 것도 실용적인 방법입니다. 한글 인코딩 문제는 R 자체의 결함이라기보다는 운영체제·프로그램마다 기본 인코딩이 다르기 때문에 생기는 문제이므로, 원본 파일이 어떤 환경에서 만들어졌는지 확인하는 것이 가장 확실한 해결책입니다.더 알아보기: 인터넷 주소(URL)에서 곧바로 읽기
read.table()·read.csv()의file인자에는 로컬 파일 경로뿐 아니라"https://"로 시작하는 웹 주소도 그대로 넣을 수 있습니다. 파일을 먼저 내려받지 않고도 곧바로 데이터프레임으로 읽어 들일 수 있어, 공개된 데이터셋을 다룰 때 특히 편리합니다.url <- "https://raw.githubusercontent.com/vincentarelbundock/Rdatasets/master/csv/datasets/iris.csv" iris_web <- read.csv(url) head(iris_web, 3) #> rownames Sepal.Length Sepal.Width Petal.Length Petal.Width Species #> 1 1 5.1 3.5 1.4 0.2 setosa #> 2 2 4.9 3.0 1.4 0.2 setosa #> 3 3 4.7 3.2 1.3 0.2 setosaCSV처럼 즉시 해석할 파일이 아니라 이미지·zip처럼 통째로 내려받아 저장부터 해야 하는 파일이라면
download.file(url, destfile, mode = "wb")을 사용합니다. 텍스트가 아닌 바이너리 파일은mode = "wb"(write binary)를 꼭 지정해야 파일이 깨지지 않습니다.download.file(url, destfile = "iris_다운로드.csv", quiet = TRUE) file.exists("iris_다운로드.csv") #> [1] TRUE사내망 등 인터넷이 연결되지 않은 환경에서는 이 두 함수 모두 사용할 수 없으니, 방화벽·프록시 설정을 먼저 확인해야 합니다.
readRDS()¶
readRDS(file, refhook = NULL)는 R 객체 하나를 그대로 저장해 둔 .rds 파일을 읽어 들여 원래의 R 객체로 복원합니다. 짝이 되는 saveRDS()와 함께 최근 R 실무에서 매우 널리 쓰이는 함수입니다.
file: 읽어 들일.rds파일 경로.refhook: 참조(reference) 형태로 저장된 특수한 객체를 복원할 때 쓰는 고급 인자로, 일반적인 용도에서는 거의 쓸 일이 없습니다.
read.csv()·read.table()이 텍스트 형태의 표 자료만 다룰 수 있는 것과 달리, readRDS()는 데이터프레임은 물론 리스트·모형 객체(lm()의 결과 등)까지 R의 어떤 객체든 원래 모습 그대로(자료형·속성 포함) 복원할 수 있습니다. 예제 파일은 다음 코드로 만들어 둡니다(saveRDS()는 다음 절에서 자세히 다룹니다).
이렇게 저장된 파일은 다음처럼 복원합니다.
성적_복원 <- readRDS("성적.rds")
성적_복원
#> 이름 국어 영어 수학 평균
#> 1 김민준 88 92 79 86.3
#> 2 이서연 95 88 84 89.0
#> 3 박도윤 76 90 95 87.0
#> 4 최지우 90 74 88 84.0
#> 5 정하은 82 85 91 86.0
load()와 달리 readRDS()는 결과를 원하는 이름의 새 변수에 직접 할당할 수 있다는 점이 큰 장점입니다(load()는 저장 당시의 변수 이름을 그대로 복원하므로 이름을 선택할 수 없습니다).
14.2 파일 저장¶
14.1절에서 읽어 들인 성적 데이터에 평균 열을 추가했습니다. 이 결과를 화면에서 확인하는 것으로 끝내지 않고, 엑셀이나 다른 통계 프로그램에서 다시 열어 볼 수 있는 파일로 남기거나, 다음 R 세션에서 곧바로 이어서 쓸 수 있는 형태로 저장해야 하는 경우가 많습니다.
앞서 만든 평균 포함 데이터프레임을 CSV로 내보내 봅니다.
성적 <- read.csv("성적.csv")
성적$평균 <- round(rowMeans(성적[2:4]), 1)
write.csv(성적, "성적_결과.csv", row.names = FALSE)
14.1절의 읽기 함수들과 마찬가지로, 저장 함수도 "표 형태로 저장해 다른 프로그램과 주고받을 것인가(write.table() 계열)"와 "R 객체 자체를 그대로 저장해 R 세션에서 이어 쓸 것인가(save() 계열)"로 나뉩니다.
write.table()¶
write.table(x, file = "", sep = " ", row.names = TRUE, col.names = TRUE, quote = TRUE, na = "NA", dec = ".", fileEncoding = "")는 데이터프레임(또는 행렬) x를 텍스트 파일로 저장합니다. read.table()과 정확히 대응되는 함수입니다.
x: 저장할 데이터프레임 또는 행렬.file: 저장할 파일 경로. 기본값""은 콘솔(화면)에 그대로 출력합니다.sep: 값 사이를 구분할 기호. 기본값은 공백 한 칸입니다.row.names:TRUE(기본값)이면 각 행 앞에 행 이름을 함께 저장합니다. 대부분의 경우 원치 않는 열이 하나 추가되는 셈이므로, 다른 프로그램과 주고받을 목적이라면FALSE로 지정하는 것이 안전합니다.col.names:TRUE(기본값)이면 첫 줄에 열 이름을 저장합니다.quote:TRUE(기본값)이면 문자형 값을 큰따옴표로 감쌉니다.na: 결측치(NA)를 파일에 어떤 문자열로 표시할지 지정합니다(기본값"NA").dec: 소수점 기호(기본값".").fileEncoding: 저장할 파일의 문자 인코딩을 지정합니다. 지정하지 않으면 현재 R 세션의 기본 인코딩을 따릅니다.
write.table(성적, "성적_결과.txt", sep = "\t", row.names = FALSE, quote = FALSE)
cat(readLines("성적_결과.txt"), sep = "\n")
#> 이름 국어 영어 수학 평균
#> 김민준 88 92 79 86.3
#> 이서연 95 88 84 89
#> 박도윤 76 90 95 87
#> 최지우 90 74 88 84
#> 정하은 82 85 91 86
write.csv() / write.csv2()¶
write.table()과 마찬가지로, 자주 쓰는 옵션 조합을 미리 지정해 둔 wrapper 함수가 있습니다. write.csv()는 sep = ",", write.csv2()는 sep = ";"·dec = ","를 기본값으로 사용하며, 나머지는 write.table()과 동일합니다. 다만 두 함수 모두 row.names의 기본값이 TRUE이고, col.names는 별도로 지정해도 무시되며 경고("attempt to set 'col.names' ignored")가 뜬다는 점에 유의해야 합니다.
write.csv(성적, "성적_결과_기본.csv") # row.names 기본값 TRUE
cat(readLines("성적_결과_기본.csv"), sep = "\n")
#> "","이름","국어","영어","수학","평균"
#> "1","김민준",88,92,79,86.3
#> "2","이서연",95,88,84,89
#> "3","박도윤",76,90,95,87
#> "4","최지우",90,74,88,84
#> "5","정하은",82,85,91,86
첫 열에 이름 없는 행 번호("1", "2", ...)가 그대로 따라온 것을 볼 수 있습니다. 데이터프레임의 행 이름이 의미 없는 단순 일련번호라면, 대부분 이 열은 필요하지 않으므로 row.names = FALSE를 지정하는 습관을 들이는 것이 좋습니다.
write.csv(성적, "성적_결과.csv", row.names = FALSE)
cat(readLines("성적_결과.csv"), sep = "\n")
#> "이름","국어","영어","수학","평균"
#> "김민준",88,92,79,86.3
#> "이서연",95,88,84,89
#> "박도윤",76,90,95,87
#> "최지우",90,74,88,84
#> "정하은",82,85,91,86
write.csv2()는 유럽식 표기를 그대로 다시 만들 때 사용합니다.
가격표 <- data.frame(제품 = c("사과", "바나나"), 가격 = c(1500.5, 2300.75))
write.csv2(가격표, "가격표_유럽식.csv", row.names = FALSE)
cat(readLines("가격표_유럽식.csv"), sep = "\n")
#> "제품";"가격"
#> "사과";1500,5
#> "바나나";2300,75
writeLines()¶
writeLines(text, con = stdout(), sep = "\n", useBytes = FALSE)는 문자형 벡터 text의 각 원소를 파일(또는 콘솔)에 한 줄씩 그대로 저장합니다. readLines()와 정확히 대응되는 함수입니다.
text: 저장할 문자형 벡터. 원소 하나가 한 줄이 됩니다.con: 저장할 파일 경로 또는 연결 객체. 기본값stdout()은 콘솔에 그대로 출력합니다.sep: 각 줄 사이에 넣을 구분 기호(기본값은 줄바꿈"\n").useBytes: 인코딩 변환 없이 원래의 바이트를 그대로 쓸지 여부를 지정하는 고급 인자입니다.
write.table()·write.csv()가 표(데이터프레임) 형태를 저장하는 데 특화된 반면, writeLines()는 형식에 상관없이 문자열을 줄 단위로 그대로 저장한다는 점이 다릅니다. 표로 정리되지 않는 메모, 로그, 보고서 초안 등을 저장할 때 적합합니다.
메모 <- c("2026-07-21 회의록", "참석자: 김민준, 이서연", "다음 회의: 8월 첫째 주")
writeLines(메모, "회의록.txt")
cat(readLines("회의록.txt"), sep = "\n")
#> 2026-07-21 회의록
#> 참석자: 김민준, 이서연
#> 다음 회의: 8월 첫째 주
save() / save.image() / load()¶
save(..., file, list = character())는 지정한 R 객체 여러 개를 원래의 변수 이름 그대로 하나의 .RData 파일에 저장합니다. save.image(file = ".RData")는 현재 세션에 있는 모든 객체를 통째로 저장하는, save(list = ls(all.names = TRUE), file = ".RData")의 편의 버전입니다. load(file)은 이렇게 저장된 파일을 읽어 저장 당시의 변수 이름 그대로 현재 세션에 복원합니다.
...: 저장할 객체들의 이름(따옴표 없이 나열).file: 저장할(또는 불러올).RData파일 경로.list: 저장할 객체 이름들을 문자형 벡터로 한꺼번에 지정하고 싶을 때 사용합니다(...대신 사용).
a <- 1
b <- "hello"
save(a, b, file = "ab.RData")
rm(a, b)
exists("a")
#> [1] FALSE
load("ab.RData")
a
b
#> [1] 1
#> [1] "hello"
rm()으로 지워졌던 a, b가 load() 이후 원래 이름 그대로 되살아난 것을 볼 수 있습니다. RStudio를 종료할 때 뜨는 "작업공간을 저장하시겠습니까?" 대화상자가 바로 이 save.image()를 호출하는 것이며, 프로젝트 폴더에 남는 .RData 파일이 그 결과물입니다.
💡 실무에서는요? 세션 전체를 통째로 저장하는
save.image()는 편리하지만, 시간이 지나면 어떤 변수가 왜 들어 있는지 알기 어려워지고 파일 용량도 불필요하게 커지기 쉽습니다. 재현 가능한(reproducible) 분석을 위해서는.RData에 의존하기보다, 필요한 객체만 아래의saveRDS()로 개별 저장하거나 코드를 처음부터 다시 실행해 결과를 재현하는 방식이 권장됩니다. 최근 RStudio도 "종료 시 .RData에 작업공간 저장" 옵션을 기본적으로 꺼 두도록(off) 안내하고 있습니다.
saveRDS()¶
saveRDS(object, file, ascii = FALSE, compress = TRUE, refhook = NULL)는 R 객체 하나를 .rds 파일로 저장합니다. readRDS()와 정확히 대응됩니다.
object: 저장할 R 객체 하나.file: 저장할.rds파일 경로.ascii:TRUE로 지정하면 사람이 읽을 수 있는 텍스트(아스키) 형태로 저장합니다(기본값은 이진(binary) 형태인FALSE).compress: 파일을 압축할지 여부(기본값TRUE).refhook:readRDS()와 마찬가지로 고급 용도의 인자입니다.
save()가 변수 이름까지 함께(그 이름 그대로 복원되도록) 저장하는 반면, saveRDS()는 객체의 값만 저장하고 이름은 저장하지 않습니다. 그 덕분에 불러올 때 원하는 새 이름으로 자유롭게 할당할 수 있어, 이름 충돌 걱정 없이 여러 결과물을 관리하기에 더 안전합니다. 사용법은 readRDS() 항목에서 이미 살펴보았으므로, 여기서는 save()/load()와의 핵심 차이만 다시 확인해 보겠습니다.
rm(성적)
성적_새이름 <- readRDS("성적.rds") # save()/load() 방식이었다면 이런 이름 변경이 불가능
성적_새이름
#> 이름 국어 영어 수학 평균
#> 1 김민준 88 92 79 86.3
#> 2 이서연 95 88 84 89.0
#> 3 박도윤 76 90 95 87.0
#> 4 최지우 90 74 88 84.0
#> 5 정하은 82 85 91 86.0
14.3 콘솔 출력¶
파일을 읽고 쓰는 것과는 별개로, 계산 과정이나 결과를 화면(콘솔)에 사람이 보기 좋은 형태로 보여줘야 할 때가 많습니다. R은 객체를 콘솔에 표시할 때 기본적으로 print()를 자동으로 호출하지만, 여러 값을 한 문장처럼 이어 붙이거나, 숫자의 자릿수·자리 구분 기호를 원하는 대로 맞추거나, 화면 대신 파일로 그 출력을 그대로 받아 내야 하는 경우에는 별도의 함수가 필요합니다.
"학생 이름과 평균 점수를 문장으로 이어 붙여 출력하기"와 "숫자를 천 단위 쉼표로 표시하기"라는 두 상황을 생각해 보겠습니다.
cat("이름:", "김민준", "/ 평균:", 86.3, "\n")
#> 이름: 김민준 / 평균: 86.3
format(1234567, big.mark = ",")
#> [1] "1,234,567"
cat()은 여러 값을 사람이 읽기 편한 하나의 문장처럼 이어 붙여 출력하는 데, print()는 R 객체를 그 자료형에 맞는 표준 형식으로 출력하는 데, format()은 값 자체는 바꾸지 않고 출력용 문자열의 모양(자릿수·정렬·자리 구분 기호 등)만 다듬는 데 각각 특화되어 있습니다. sink()는 이 콘솔 출력의 "목적지"를 화면이 아니라 파일로 바꿔치기하는 함수입니다.
cat()¶
cat(..., file = "", sep = " ", fill = FALSE, append = FALSE)는 여러 값을 이어 붙여 콘솔(또는 파일)에 그대로 출력합니다.
...: 출력할 값들. 벡터·문자열·숫자를 자유롭게 섞어 나열할 수 있습니다.file: 출력할 파일 경로. 기본값""은 콘솔에 출력합니다.sep: 값들 사이에 넣을 구분 기호(기본값은 공백 한 칸).fill:TRUE(또는 숫자)로 지정하면 출력 폭에 맞춰 자동으로 줄바꿈합니다.append:TRUE로 지정하면 기존 파일 내용 뒤에 이어서 씁니다(기본값FALSE는 파일을 덮어씁니다).
print()와 달리 cat()은 따옴표나 [1] 같은 색인 표시 없이 값을 그대로 이어 붙인다는 점이 핵심입니다.
sep 인자로 값들 사이의 구분 기호를 자유롭게 바꿀 수 있습니다.
cat("이름:", "김민준", "국어:", 88, "영어:", 92, sep = " / ")
cat("\n")
cat(1:5, sep = ", ")
cat("\n")
#> 이름: / 김민준 / 국어: / 88 / 영어: / 92
#> 1, 2, 3, 4, 5
주의:
sep은cat()에 나열한 인자들 "사이"에만 들어가는 것이 아니라,cat()에 나열된 모든 값을 하나의 긴 나열로 취급해 그 사이사이에 똑같이 끼워 넣습니다. 위 예제에서"이름:"과"김민준"사이뿐 아니라"김민준"과"국어:"사이에도" / "가 들어간 것이 바로 그 예입니다. 값들을 의도한 자리에서만 구분하고 싶다면paste()로 먼저 하나의 문자열을 만든 뒤cat()에 넘기는 것이 안전합니다.
print()¶
print(x, ...)는 R 객체 x를 그 자료형에 맞는 표준 형식으로 출력합니다. 콘솔에 객체 이름만 입력했을 때 자동으로 호출되는 함수가 바로 이 print()입니다.
x: 출력할 객체....: 객체의 클래스에 따라 다른 세부 옵션이 전달됩니다. 예를 들어 문자형 벡터에는quote(따옴표 표시 여부), 숫자에는digits(유효 자릿수) 등을 지정할 수 있습니다.
data.frame·lm 모형처럼 클래스가 있는 객체를 print()에 넘기면, R은 그 클래스 전용의 print.data.frame()·print.lm() 같은 메서드를 자동으로 찾아 호출합니다(19.2절 S3 클래스에서 이 메서드 탐색 규칙을 자세히 다룹니다). 콘솔에 객체 이름만 쳤을 때 객체 종류마다 다른 모양으로 출력되는 이유가 바로 이 메서드 디스패치(dispatch) 덕분입니다.
format()¶
format(x, digits = NULL, nsmall = 0L, width = NULL, big.mark = "", scientific = NA, justify = "left", ...)는 값 자체는 바꾸지 않고, 출력용 문자열로 변환하면서 자릿수·정렬·자리 구분 기호 등의 모양을 다듬습니다.
x: 서식을 적용할 값(숫자·문자·날짜 등).digits: 표시할 유효 자릿수.nsmall: 소수점 이하 최소 자릿수. 정수만 지정하는digits와 달리, 끝자리가 0이라도 자릿수를 채워 표시하고 싶을 때 사용합니다.width: 결과 문자열의 최소 너비. 값이 이보다 짧으면 빈칸을 채워 맞춥니다.big.mark: 천 단위 등 자리 구분에 사용할 기호(예:",").scientific:TRUE이면 지수 표기(예:1e+05),FALSE이면 일반 표기를 강제합니다. 기본값NA는 R이 상황에 맞게 자동으로 선택합니다.justify: 문자형 값의 정렬 방향("left"(기본값)·"right"·"centre").
format(3.14159, digits = 3)
#> [1] "3.14"
format(3.1, nsmall = 3)
#> [1] "3.100"
format(1234567, big.mark = ",")
#> [1] "1,234,567"
width를 지정하면 여러 값의 자릿수를 맞춰 표 형태로 나란히 보여 줄 때 특히 유용합니다.
날짜에도 그대로 적용되어, 원하는 표기 형식으로 바꿀 수 있습니다.
💡
format()과formatC(), 뭐가 다를까요?formatC()도 값을 서식 문자열로 바꾸는 함수라는 점은 같지만, C 언어의printf서식 규칙(%d,%f,%e등)에 더 가까운 세부 제어를 제공합니다. 예를 들어 정수 앞을 0으로 채우거나(flag = "0"), 지수 표기의 자릿수를 정확히 지정하고 싶을 때는formatC()가 더 직관적입니다.formatC(7, width = 3, flag = "0") # 앞을 0으로 채움 formatC(0.000123, format = "e", digits = 2) # 지수 표기 #> [1] "007" #> [1] "1.23e-04"반대로 벡터 전체의 자릿수를 데이터에 맞춰 자동으로 정렬해 주는 것은
format()쪽이 더 편리합니다. 두 함수 모두 결과가 문자형이라는 점은 같으므로, 서식을 적용한 값을 다시 숫자로 계산에 쓰려면as.numeric()으로 되돌려야 합니다.
sink()¶
sink(file = NULL, append = FALSE, type = c("output", "message"), split = FALSE)는 콘솔에 출력될 내용을 화면 대신 파일로 돌려보냅니다("output을 가라앉힌다"는 의미의 이름입니다).
file: 출력을 받아 낼 파일 경로.NULL(기본값)을 지정하면 다시 콘솔 출력으로 되돌립니다.append:TRUE로 지정하면 기존 파일 내용 뒤에 이어서 씁니다.type:"output"(기본값, 일반 출력)과"message"(경고·메시지) 중 어느 것을 파일로 돌려보낼지 지정합니다.split:TRUE로 지정하면 파일에 저장하면서 동시에 콘솔에도 출력합니다(기본값FALSE는 파일에만 저장).
sink()가 실행되고 있는 동안에는 print()·cat()을 포함한 거의 모든 콘솔 출력이 지정한 파일로 저장되며, 인자 없이 sink()를 한 번 더 호출해야 다시 화면으로 돌아옵니다. 이 짝을 맞추지 않으면 이후의 모든 출력이 계속 파일로 사라져 버리므로, sink()를 열었다면 반드시 닫아야 한다는 점을 기억해야 합니다.
sink("결과.txt")
cat("이것은 파일에 저장됩니다.\n")
print(summary(1:10))
sink() # 다시 콘솔로 복귀
cat("콘솔로 복귀했습니다. 파일 내용:\n")
cat(readLines("결과.txt"), sep = "\n")
#> 콘솔로 복귀했습니다. 파일 내용:
#> 이것은 파일에 저장됩니다.
#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> 1.00 3.25 5.50 5.50 7.75 10.00
주의: 코드 중간에 오류가 나서
sink()를 닫는 코드가 실행되지 못하면, 이후 콘솔에 아무것도 출력되지 않는 것처럼 보여 당황하기 쉽습니다.sink()는 인자 없이 여러 번 호출해도 안전하므로(더 이상 열려 있는 sink가 없으면 아무 일도 일어나지 않음),while (sink.number() > 0) sink()처럼 남아 있는 모든 sink를 확실히 닫아 주는 방어적인 코드를 함께 써 두면 안전합니다.